package com.swapper.math.utils;

import java.math.BigDecimal;
import java.math.MathContext;
import java.math.RoundingMode;

public class BigDecimalUtils {
  private static final BigDecimal BIG_TWO = BigDecimal.valueOf(2L);

  private BigDecimalUtils() {
    throw new UnsupportedOperationException(getClass().getSimpleName());
  }

  public static BigDecimal sqrt(BigDecimal decimal) {
    return sqrt(decimal, 8, new MathContext(32, RoundingMode.HALF_EVEN));
  }

  public static BigDecimal sqrt(BigDecimal decimal, int scale, MathContext mc) {
    int compare = decimal.compareTo(BigDecimal.ZERO);
    if (compare < 0) {
      throw new ArithmeticException("the square root of a negative number.");
    }
    if (compare == 0) {
      return BigDecimal.ZERO;
    }
    if (decimal.equals(BigDecimal.ONE)) {
      return BigDecimal.ONE;
    }
    BigDecimal result = decimal;
    int precision = mc.getPrecision();
    for (int i = 0; i < precision; ++i) {
      result = result.add(decimal.divide(result, mc)).divide(BIG_TWO, mc);
    }
    return result.setScale(scale, mc.getRoundingMode());
  }

  public static BigDecimal avg(BigDecimal[] decimals, int scale, MathContext mc) {
    BigDecimal sum = BigDecimal.ZERO;
    BigDecimal num = BigDecimal.valueOf(decimals.length);
    for (BigDecimal decimal : decimals) {
      sum = sum.add(decimal);
    }
    return sum.divide(num, mc);
  }

  @SuppressWarnings("all")
  public static BigDecimal md(BigDecimal[] decimals, int scale, MathContext mc) {
    BigDecimal sum = BigDecimal.ZERO;
    BigDecimal num = BigDecimal.valueOf(decimals.length);
    for (BigDecimal decimal : decimals) {
      sum = sum.add(decimal);
    }
    BigDecimal avg = sum.divide(num, mc);
    BigDecimal tmp = BigDecimal.ZERO;
    for (BigDecimal decimal : decimals) {
      tmp = tmp.add(decimal.subtract(avg).abs());
    }
    tmp = tmp.divide(num, mc);
    return tmp.setScale(scale, mc.getRoundingMode());
  }

  @SuppressWarnings("all")
  public static BigDecimal sd(BigDecimal[] decimals, int scale, MathContext mc) {
    BigDecimal sum = BigDecimal.ZERO;
    BigDecimal num = BigDecimal.valueOf(decimals.length);
    for (BigDecimal decimal : decimals) {
      sum = sum.add(decimal);
    }
    BigDecimal avg = sum.divide(num, mc);
    BigDecimal tmp = BigDecimal.ZERO;
    for (BigDecimal decimal : decimals) {
      tmp = tmp.add(decimal.subtract(avg).pow(2));
    }
    tmp = tmp.divide(num.subtract(BigDecimal.ONE), mc);
    return sqrt(tmp, scale, mc);
  }

  public static String toPlainString(double value) {
    return toPlainString(value, 4, RoundingMode.HALF_EVEN);
  }

  public static String toPlainString(double value, int scale, RoundingMode roundingMode) {
    return BigDecimal.valueOf(value).setScale(scale, roundingMode).toPlainString();
  }
}
